package com.esdk.utils;

import com.esdk.esdk;
import com.esdk.interfaces.FuncNoArg;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Objects;
import java.util.function.Consumer;


/**
 * 类似于Stream，此类针对单个对象继续处理，而不是集合
 * 该类的作用是为了调用函数时能够忽略空值，少写判空
 * @author zhangzhexin
 * @since 2025.01.09
 */
public class Nullable<T> {
    private T val;

    private Runnable findNullAfterFilter = null;

    public interface Func<T,R>{
        R invoke(T t) throws Throwable;
    }
    public interface NoReturnFunc<T>{
        void invoke(T t) throws Throwable;
    }
    public interface DFunc<T,D,R>{
        R invoke(T t, D d) throws Throwable;
    }
    public interface DNoReturnFunc<T,D>{
        void invoke(T t, D d) throws Throwable;
    }

    public static <R> Nullable<R> of(R val){
        return new Nullable<>(val);
    }

    public static <R> Nullable<R> or(R... vals){
        return new Nullable<>(esdk.obj.or(vals));
    }

    /**排除空字符值或空集合或空数组*/
    public static <R> Nullable<R> valid(R val){
         if(esdk.obj.isValid(val)){
             return new Nullable<>(val);
         }
         return new Nullable<>(null);
    }

    public Nullable(T val) {
        this.val = val;
    }

    public T get(){
        return val;
    }

    public T get(T defaultVal){
        if(val == null) return defaultVal;
        return val;
    }

    public T get(FuncNoArg<T> defProvider){
        if(val == null){
          try{
            return defProvider.invoke();
          }catch(Exception e){
            throw esdk.tool.wrapThrowble(e);
          }
        }
        return val;
    }

    public boolean isNotNull(){
        return val != null;
    }

    public boolean isNull(){
        return val == null;
    }
    

    public Nullable<T> findNullAfterFilter(NoReturnFunc<T> func){
        if(func == null) findNullAfterFilter = null;
        findNullAfterFilter = () -> {
            try {
                func.invoke(val);
            } catch (Throwable e) {
                throw esdk.tool.wrapThrowble(e);
            }
        };
        return this;
    }

    public <V> Nullable<T> checkIsNotNull(){
        return check(v -> esdk.obj.isValid(v));
    }

    public <V> Nullable<T> check(Func<T,V> mapFunc,Func<V,Boolean> checkFunc){
        if(val==null) return this;
        return check(v -> checkFunc.invoke(mapFunc.invoke(v)));
    }

    public <V, Arg> Nullable<T> check(Func<T,V> mapFunc,DFunc<V, Arg, Boolean> checkFunc,Arg arg){
        if(val==null) return this;
        return check(v -> checkFunc.invoke(mapFunc.invoke(v),arg));
    }

    public Nullable<T> check(Func<T,Boolean> func){
        if(val != null) {
            try {
                Boolean res = func.invoke(val);
                if(res == null || !res) val = null;
            } catch (Throwable e) {
                throw esdk.tool.wrapThrowble(e);
            }
        }
        if(val == null && findNullAfterFilter != null) {
            findNullAfterFilter.run();
        }
        return this;
    }

    public <Arg> Nullable<T> check(DFunc<T, Arg, Boolean> func,Arg arg){
        if(val != null) {
            try {
                Boolean res = func.invoke(val, arg);
                if(res != null && !res) val = null;
            } catch (Throwable e) {
                throw esdk.tool.wrapThrowble(e);
            }
        }
        if(val == null && findNullAfterFilter != null) {
            findNullAfterFilter.run();
        }
        return this;
    }

    public Nullable<T> exclude(Func<T,Boolean> func){
        if(val != null) {
            try {
                Boolean res = func.invoke(val);
                if(res != null && res) val = null;
            } catch (Throwable e) {
                throw esdk.tool.wrapThrowble(e);
            }
        }
        if(val == null && findNullAfterFilter != null) {
            findNullAfterFilter.run();
        }
        return this;
    }


    public <Arg> Nullable<T> exclude(DFunc<T, Arg, Boolean> func,Arg arg){
        if(val != null) {
            try {
                Boolean res = func.invoke(val, arg);
                if(res == null || res) val = null;
            } catch (Throwable e) {
                throw esdk.tool.wrapThrowble(e);
            }
        }
        if(val == null && findNullAfterFilter != null) {
            findNullAfterFilter.run();
        }
        return this;
    }

    /**如果val不为null则调用函数接口，没有返回值*/
    public Nullable<T> act(NoReturnFunc<T> func){
        if(val == null) return this;
        try {
            func.invoke(val);
            return this;
        } catch (Throwable e) {
            throw esdk.tool.wrapThrowble(e);
        }
    }

    /**如果val不为null则调用函数接口，没有返回值
     * @param arg 函数接口传参
     * */
    public <Arg> Nullable<T> act(DNoReturnFunc<T, Arg> func,Arg arg){
        if(val == null) return this;
        try {
            func.invoke(val, arg);
            return this;
        } catch (Throwable e) {
            throw esdk.tool.wrapThrowble(e);
        }
    }

    /**如果val为null则不处理，有返回值*/
    public <R> Nullable<R> call(Func<T, R> func){
        if(val == null) return (Nullable<R>) this;
        try {
            return new Nullable<>(func.invoke(val));
        } catch (Throwable e) {
            throw esdk.tool.wrapThrowble(e);
        }
    }

    /**如果val为null则不处理，有返回值*/
    public <R, Arg> Nullable<R> call(DFunc<T, Arg, R> func,Arg arg){
        if(val == null) return (Nullable<R>) this;
        try {
            return new Nullable<>(func.invoke(val, arg));
        } catch (Throwable e) {
            throw esdk.tool.wrapThrowble(e);
        }
    }

    public <R> Nullable<R> to(Class<R> rClass){
        return (Nullable<R>) this;
    }

    public static void main(String[] args) {
        esdk.tool.wrapThrowble(new FileNotFoundException("dddd"));
        Nullable.of(new ArrayList<String>()).act(Iterable::forEach, (Consumer<String>) System.out::println);
        Nullable.of(new StringBuffer())
          .call(StringBuffer::append, "")
          .exclude(Objects::isNull)
          .act(StringBuffer::reverse)
          .act(StringBuffer::setLength, 10)
          .call(StringBuffer::length)
          .get();
    }
}
